跳转到内容
返回

Astro SSG 国际化

背景

最近重新 “装潢” 了一下本站,从 Hexo 迁移到了 Astro,主题用的 astro-paper ,但是这套主题没有适配国际化,文案默认用的英文,但本站主要内容都是中文撰写,中英混着有点奇怪,索性自己手搓适配了一套国际化方案,第一次给 Astro 开发这么多,也有一些坑,简单记录下

实现

文案管理

参考spa应用的国际化库,用 json 来管理不同语种文案

export const LANG = {
  en: {
    "header.skip_to_content": "Skip to content",
    "header.posts": "Posts",
    "header.tags": "Tags",
    "header.fragments": "Fragments",
    //  ..
  },
  zh: {
    "header.skip_to_content": "跳转到内容",
    // ..
  },
};

UI层消费

UI层使用文案时,使用统一提供的文案函数来获取对应文案,函数内会自动识别当前全局在展示的语种等一系列通用操作,UI层只需关心文案key即可

export type LANG_KEY = keyof typeof LANG;

export type LANG_KEY_ITEM = keyof (typeof LANG)["en"];

export const t = (key: LANG_KEY_ITEM, ...args: string[]): string => {
  const KEY = (defaultLang || SITE.lang || "en") as LANG_KEY;
  let text = LANG[KEY][key];
  // 替换参数 {0}, {1}, {2}, ...
  if (args.length > 0) {
    args.forEach((arg, index) => {
      text = text.replace(`{${index}}`, arg);
    });
  }

  return text;
};

这里需要注意,除过 DOM 层直接展示,还有 JS逻辑 里进行判断以及逻辑输出进行组合来下发给UI展示的文案,所以函数也要做出对应的实现,如下

// 正常使用
t("ride.recent_distance_progress");

// 组合使用
const recentDistanceKm = 100;
t("ride.recent_distance_km", recentDistanceKm);

语言切换

坑之一,这套主题默认是走的 Astro 的 SSG 模式去run的,即在构建时生成所有的HTML,然后部署构建产物;所以能做的操作就会有2套:

CSR 版本

为了切合 Astro 框架的理念,不想有太多客户端运行时,就没用这种了,简单写下思路

因为SSG在构建的时候,会执行所有的逻辑然后进行替换,上述的 t('xxx') 函数会在构建后直接生成为对应的文案,而不是 函数表达式 ,所以要走 CSR 实现就得在 消费函数里改成返回一种 抽象标识 等类似的产物,然后同时再下发一段用来识别该产物的JS逻辑,用于在客户端执行语言切换时来进行替换不同的语言

但对应到上述提到的2类使用case后,返回的标识产物要做不同的处理

  1. HTML里展示:这类很好处理,返回一个 ID为当前语言key的DOM,浏览器里直接替换该DOM即可
  2. JS里附带逻辑后给到HTML展示的内容:这种就得兼容业务逻辑的执行时间和替换逻辑的执行顺序问题,保证替换逻辑在所有页面逻辑之后执行即可

构建时版本

最后采用的方案

这种就简单很多,既然部署产物是预先构建好的,那只用在构建的时候对需要的语言都构建一份即可,切换语言逻辑也很简单,重定向到不同语言版本URL即可

构建多语言页面

官方有一个相关实践指南 I18n,但这里写的都是让对每一个页面建一个页面,很冗余,可以利用 Astro 的 动态路由 来达到一样的效果

  1. 在pages下新建动态目录并在下方新建所有页面对应页面组件
src
├── pages
│   ├── index.astro
│   ├── [locale]
│   │   ├── index.astro

上方pages内的组件均为不带语言标识的path对应的页面组件,[locale] 内的均为多语言path下访问的页面组件

  1. 抽离UI逻辑到UI组件内,然后在页面组件引入来复用UI部分
  2. 在多语言组件内声明支持的语言path来在访问时生效

如下是local/index.astro的内容

---
import Home from "@/pages/index.astro";

// 定义支持的非默认语言路由 (for SSG)
export function getStaticPaths() {
  return [
    { params: { locale: "en" } },
    // 添加更多语言
  ];
}
---

<Home />

Astro的踩坑

组件的script脚本只执行一次

Astro的组件内的js逻辑只在第一次访问生效,后续没反应,无论是否启用客户端渲染都如此;这里主要是 Astro 的一些机制会复用DOM结构,但不会复用js逻辑,也不会像普通模板引擎一样,直接挂一个纯csr的js脚本进去,需要用默认提供的生命周期函数包裹一下,来处理,如下

astro:page-load

document.addEventListener("astro:page-load", () => {
  // ...
});

页面组件和UI组件

UI组件和传统 SPA 应用的组件没什么区别,承载UI展示和UI交互的逻辑;

页面组件会耦合 Astro的构建来注入处理一些东西,例如包含下列操作后,只有页面组件可以用

getStaticPaths

prerender



上一篇
safari截图问题
下一篇
更简单的图片懒加载实现
×